原文:https://www.sitepoint.com/self-documenting-javascript/

最近再捡起来重新学习英文,看英文技术文档,看到这篇感觉比较容易试着翻译下。

作者: Jani Hartikainen

Jani has built all kinds of JS apps for more than 15 years. At his blog, he helps JavaScript developers learn to eliminate bad code so they can focus on writing awesome apps and solve real problems.

Jani有超过15年的经验为不同类型的JS应用;在他的博客上,他帮助js开发者学习怎么去避免糟糕的代码使其聚焦于开发出卓越的应用,并解决真实的问题。

It’s an easy mistake to make: you change some code, and forget to remove or update the comment. A bad comment won’t break your code, but imagine what would happen when debugging. You read the comment. It says one thing, while the code does another. You’ll probably end up wasting time figuring it out, and in the worst case, it might even mislead you!

这是一个容易犯的错误:你改变一些代码,而忘记删除或更新注释。错误注释不影响代码逻辑,但想一下,在调试的时候会发生什么。你读了注释。它说的是一个结果,而代码执行的是另一个结果。你可能最终会浪费时间想出来的,而在最坏的情况下可能会误导你!

But writing code with zero comments isn’t an option. In my more than 15 years of programming experience, I’ve never seen a codebase where comments were completely unnecessary.

但零注释写代码是不是一种选择。在我15年以上的编程经验,我从来没有见过一个代码库,注释是完全无用的。

However, there are ways to reduce the need for comments. We can make use of certain coding techniques to clarify our code, simply by using the programming language’s features to our advantage.

然而,有一些方法,以减少注释的必要性。 我们可以规范编码来阐明代码用处,只需使用的编程语言的特性给我们发挥优势。

Not only does this help make our code easier to understand, it can also help improve the design of the program overall!

不仅能使我们能容易理解代码,他也能帮助我们提高我们程序的总体设计。

This type of code is often called self documenting. Let me show you how you can take this approach to coding right now. While the examples I’ll present here are in JavaScript, you can apply most of the techniques in other languages as well.

这种目的规范代码通常被叫做(self-documenting)自带注释, 让我展示给你如何让你的代码接近这个目的,在这儿我将要用 js 去作为例子,你也能延伸到其他语言代码。

Overview of Techniques 技术概述

Some programmers include comments as part of self-documenting code. In this article, we’ll only focus on code. Comments are important, but they’re a large topic to be covered separately.

一些程序包括代码注释部分也是 self-documeting 代码的一部分,在这个文档中我们聚焦于代码,注释也是很重要的它是个大的课题单独去研究。

We can split the techniques for self-documenting code into three broad categories:

我们把 self-documenting技术方案分成了三个主题,

structural, where the structure of code or directories is used to clarify the purpose
naming related, such as function or variable naming
syntax related, where we make use of (or avoid using) features of the language to clarify code.

结构: 清晰的代码或者目录的结构来达到目的
命名:函数或者变量命名
语法: 能使用或者避免使用一些语言特性来达到目的

Many of these are simple on paper. The challenge comes from knowing when to use what technique. I’ll show you some practical examples as we tackle each one.

纸上谈兵终觉浅,这最大的挑战是知道什么时候用什么技术,我将要展示些实际例子以便我们解决每个问题。

Structural 结构

First, let’s look at the structural category. Structural changes refer to shifting code around for enhanced clarity.

Move code into a function
This is the same as the “extract function” refactoring — meaning that we take existing code and move it into a new function: we “extract” the code out into a new function.

移动代码进入函数内

这是相同的和『extract function』-意思是把已经存在的代码进入个新的函数内;

试着去猜下面这一行是干什么的:

var width = (value - 0.5) * 16;

Not very clear; a comment here could be quite useful. Or, we could extract a function to make it self documenting:

不是非常清楚,一个注释在这儿应该或者『extract function』提取函数

var width = emToPixels(value);

function emToPixels(ems) {
    return (ems - 0.5) * 16;
}

The only change was I moved the calculation into a function. The function’s name is descriptive of what it does, so the code no longer needs clarification. As an additional benefit, we now have a useful helper function that you can use elsewhere, so this method also helps reduce duplication.

我们仅有的改变是我把计算移动到了一个函数内,这函数的名字描述它是干什么的,由此代码也不需要怎么注释了。还有个额外好处,这函数可以再其他地方调用从而减少代码重复。

Replace conditional expression with function 用函数代替条件表达式

If clauses with multiple operands can often be hard to understand without a comment. We can apply a similar method as above to clarify them:

如果没有注释,多个条件表达式是很难理解的我们可以用上面相似的方式使其看起来更明白

1
2
if(!el.offsetWidth || !el.offsetHeight) {
}

上面条件的目的是什么?

1
2
3
4
5
6
function isVisible(el) {
return el.offsetWidth && el.offsetHeight;
}

if(!isVisible(el)) {
}

Again, we moved the code into a function and the code is immediately much easier to understand.
同样,我们把代码移动到函数中立即看起来更加容易理解了

Replace expression with variable 用变量代替表达式

Replacing something with a variable is similar to moving code into a function, but instead of a function, we simply use a variable.

用变量代替一些语句是相似的同把代码移动到一个函数中,但是作为函数的替代品变量更简单。

Let’s take a look at the example with if clauses again:

再让我们看这个条件语句

1
2
if(!el.offsetWidth || !el.offsetHeight) {
}

Instead of extracting a function, we can also clarify this by introducing a variable:

作为函数的替代品引人个变量

1
2
3
var isVisible = el.offsetWidth && el.offsetHeight;
if(!isVisible) {
}

This can be a better choice than extracting a function — for example, when the logic you want to clarify is very specific to a certain algorithm used only in one place.

The most common use for this method is mathematical expressions:

这可能是更好的选择比引人函数,再一些仅仅使用一次的逻辑代码中;

最常用的方法是数学表达式

1
return a * b + (c / d);

We can clarify the above by splitting the calculation:

我们可以拆分如下表达式:

1
2
3
var multiplier = a * b;
var divisor = c / d;
return multiplier + divisor;

Because I’m terrible at math, imagine the above example has some meaningful algorithm. In any case, the point is that you can move complex expressions into variables that add meaning to otherwise hard-to-understand code.

因为我讨厌数学,无法想象上面的例子是什么意思无论任何情况,这问题是你可以把复杂的表达式移动到一个变量意味着用其他方式理解代码。

Class and module interfaces类和模块接口

The interface — that is, the public methods and properties — of a class or module can act as documentation on its usage.

接口 - 即按照使用文档可以使用的类或者模块的公共方法和属性。

Let’s look at an example:

让我们来看一个例子。

1
2
3
4
5
6
7
8
9
class Box {
setState(state) {
this.state = state;
}

getState() {
return this.state;
}
}

This class could contain some other code in it as well. I purposely kept the example simple, to illustrate how the public interface is documentation

这个类另外还包含了一些其他代码。我特意保留了简单的例子,来说明公共接口就是文档

Can you tell how this class should be used? Maybe with a little bit of work, but it isn’t very obvious.

你可以告诉这个类应该怎么使用?可能使用中有点思路,但不是非常肯定

Both of the functions have reasonable names: what they do is clear from their name. But despite this, it’s not very clear how you should be using them. Most likely you would need to read more code or the documentation for the class to figure it out.

这两个函数又合适的名字,从他们的名字我们清楚的知道是干什么的但尽管这样,我们还不是真清楚应该如何使用他们,最有可能的,你将需要更多代码或弄明白的文档类。

What if we changed it to something like this:

如果我们把它改成这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Box {
open() {
this.state = 'open';
}

close() {
this.state = 'closed';
}

isOpen() {
return this.state === 'open';
}
}

Much easier to see the usage, don’t you think? Notice that we only changed the public interface; the internal representation is still the same with the this.state property.

看使用容易得多,你不觉得吗?请注意,我们不仅改变了公共接口;内部表示仍是相同和this.state属性。

Now you can tell at a glance how the Box class is used. This shows that even though the first version had good names in the functions, the complete package was still confusing, and how, with simple decisions like this, you can have a very big impact. You always have to think of the big picture.

现在,你可以一目了然告诉Box类如何使用。这表明,即使第一个版本曾有表达功能好的名字,完整的方案仍然扑朔迷离,如果像如何像这样简单的决策,你可以有非常大的效果。你通常不得不要思考大的范围;

##Code grouping 代码分组

Grouping different parts of code can also act as a form of documentation.

代码分组也可以作为文档的一部分

For example, you should always aim to declare your variables as close to where they are being used as possible, and try to group variable uses together.

例如,应该尽量把变量声明接近使用,试着按照使用去分组变量;

This can be used to indicate a relationship between the different parts of the code, so that anyone changing it in the future has an easier time finding which parts they may also need to touch.

这样就暗示了代码不同部分直接的关系,将来改变的时候可以和容易的找到

Consider the following example:

思考下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var foo = 1;

blah()
xyz();

bar(foo);
baz(1337);
quux(foo);
Can you see at a glance how many times foo was used? Compare it to this:

var foo = 1;
bar(foo);
quux(foo);

blah()
xyz();

baz(1337);

With all the uses of foo grouped together, we can easily see which parts of the code depend on it.
foo的分到一个单元内,我们能很容易的看到那个代码依赖于它

Use pure functions 使用纯函数

Pure functions are much easier to understand than functions that rely on state.
纯函数更容易理解比依赖性函数

What is a pure function? When calling a function with the same parameters, if it always produces the same output, it’s most likely a so-called “pure” function. This means the function should not have any side effects or rely on state — such as time, object properties, Ajax, etc.

什么是纯函数? 它类似于输出类似的参数,通常又相同的输出的『pure』函数。也就意味着此函数不能又其他反馈或者依赖一些状态日时间,对象属性或者ajax等等;

These types of functions are easier to understand, as any values affecting their output are passed in explicitly. You won’t have to dig around to figure out where something comes from, or what affects the result, as it’s all in plain sight.

Another reason these types of functions make for more self-documenting code is you can trust their output. No matter what, the function will always return output only based on what parameters you give it. It also won’t affect anything external, so you can trust it won’t cause an unexpected side effect.

A good example of where this goes wrong is document.write(). Experienced JS developers know you shouldn’t use it, but many beginners stumble with it. Sometimes it works well — but other times, in certain circumstances, it can wipe the whole page clean. Talk about a side effect!

For a better overview of what a pure function is, see the article Functional Programming: Pure Functions.

Directory and file structure
When naming files or directories, follow the same naming convention as used in the project. If there’s no clear convention in the project, follow the standard for your language of choice.

For example, if you’re adding new UI-related code, find where similar functionality is in the project. If UI-related code is placed in src/ui/, you should do the same.

This makes it easier to find the code and shows its purpose, based on what you already know about the other pieces of code in the project. All UI code is in the same place, after all, so it must be UI related.

Naming
There’s a popular quote about the two hard things in computer science:

There are only two hard things in Computer Science: cache invalidation and naming things. — Phil Karlton
So let’s take a look at how we can use naming things to make our code self documenting.

Rename function
Function naming is often not too difficult, but there’s some simple rules that you can follow:

Avoid using vague words like “handle” or “manage”: handleLinks(), manageObjects(). What do either of these do?
Use active verbs: cutGrass(), sendFile() — functions that actively perform something.
Indicate return value: getMagicBullet(), readFile(). This is not something you can always do, but it’s helpful where it makes sense.
Languages with strong typing can use type signatures to help indicate return values as well.
Rename variable
With variables, here are two good rules of thumb:

Indicate units: if you have numeric parameters, you can include the expected unit. For example, widthPx instead of width to indicate the value is in pixels instead of some other unit.
Don’t use shortcuts: a or b are not acceptable names, except for counters in loops.
Follow established naming conventions
Try to follow the same naming conventions in your code. For example, if you have an object of a specific type, call it the same name:

var element = getElement();
Don’t suddenly decide to call it a node:

var node = getElement();
If you follow the same conventions as elsewhere in the codebase, anyone reading it can make safe assumptions about the meanings of things based on what it means elsewhere.

Use meaningful errors
Undefined is not an object!

Everyone’s favorite. Let’s not follow JavaScript’s example, and let’s make sure any errors our code throws have a meaningful message in them.

What makes an error message meaningful?

it should describe what the problem was
if possible, it should include any variable values or other data which caused the error
key point: the error should help us find out what went wrong — therefore acting as documentation on how the function should work.
Syntax
Syntax-related methods for self-documenting code can be a little bit more language specific. For example, Ruby and Perl allow you to do all kinds of strange syntax tricks, which, in general, should be avoided.

Let’s take a look at a few that happen with JavaScript.

Don’t use syntax tricks
Don’t use strange tricks. Here’s a good way to confuse people:

imTricky && doMagic();
It’s equivalent to this much more sane looking code:

1
2
3
if(imTricky) {
doMagic();
}

Always prefer the latter form. Syntax tricks are not going to do anyone any favors.

Use named constants, avoid magic values
If you have special values in your code — such as numbers or string values — consider using a constant instead. Even if it seems clear now, more often than not, when coming back to it in a month or two, nobody will have any idea why that particular number was put there.

const MEANING_OF_LIFE = 42;
(If you’re not using ES6, you can use a var and it’ll work equally well.)

Avoid boolean flags
Boolean flags can make for hard-to-understand code. Consider this:

myThing.setData({ x: 1 }, true);
What is the meaning of true? You have absolutely no idea, unless you dig into the source for setData() and find out.

Instead, you can add another function, or rename an existing function:

myThing.mergeData({ x: 1 });
Now you can immediately tell what’s going on.

Use language features to your advantage
We can even use some features of our chosen language to better communicate the intention behind some code.

A good example of this in JavaScript are the array iteration methods:

1
2
3
4
var ids = [];
for(var i = 0; i < things.length; i++) {
ids.push(things[i].id);
}

The above code collects a list of IDs into a new array. However, in order to know that, we need to read the whole body of the loop. Compare it with using map():

1
2
3
var ids = things.map(function(thing) {
return thing.id;
});

In this case, we immediately know that this produces a new array of something, because that’s the purpose of map(). This can be beneficial especially if you have more complicated looping logic. There’s a list of other iteration functions on MDN.

Another example with JavaScript is the const keyword.

Often, you declare variables where the value is supposed to never change. A very common example is when loading modules with CommonJS:

var async = require(‘async’);
We can make the intention of never changing this even more clear:

const async = require(‘async’);
As an added benefit, if someone ever accidentally tries to change this, we’ll now get an error.

Anti-patterns
With all these methods at your disposal, you can do a lot of good. However, there are some things you should be careful about …

Extracting for the sake of having short functions
Some people advocate the use of tiny tiny functions, and if you extract everything out, that’s what you can get. However, this can detrimentally affect how easy the code is to understand.

For example, imagine you’re debugging some code. You look in function a(). Then, you find it uses b(), which then uses c(). And so on.

While short functions can be great and easy to understand, if you’re only using the function in a single place, consider using the “replace expression with variable” method instead.

Don’t force things
As usual, there’s no absolute right way to do this. Therefore, if something doesn’t seem like it’s a good idea, don’t try to force it.

Conclusion
Making your code self documenting goes a long way to improving the maintainability of your code. Every comment is additional cruft that has to be maintained, so eliminating comments where possible is a good thing.

However, self-documenting code doesn’t replace documentation or comments. For example, code is limited in expressing intent, so you need to have good comments as well. API documentation is also very important for libraries, as having to read the code is not feasible unless your library is very small.

待续~~~